home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / chrome / browser.jar / content / browser / places / utils.js < prev   
Encoding:
JavaScript  |  2009-07-15  |  48.2 KB  |  1,339 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Places Command Controller.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Myk Melez <myk@mozilla.org>
  24.  *   Asaf Romano <mano@mozilla.com>
  25.  *   Sungjoon Steve Won <stevewon@gmail.com>
  26.  *   Dietrich Ayala <dietrich@mozilla.com>
  27.  *   Marco Bonardo <mak77@bonardo.net>
  28.  *
  29.  * Alternatively, the contents of this file may be used under the terms of
  30.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  31.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  32.  * in which case the provisions of the GPL or the LGPL are applicable instead
  33.  * of those above. If you wish to allow use of your version of this file only
  34.  * under the terms of either the GPL or the LGPL, and not to allow others to
  35.  * use your version of this file under the terms of the MPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the notice
  37.  * and other provisions required by the GPL or the LGPL. If you do not delete
  38.  * the provisions above, a recipient may use your version of this file under
  39.  * the terms of any one of the MPL, the GPL or the LGPL.
  40.  *
  41.  * ***** END LICENSE BLOCK ***** */
  42.  
  43. function LOG(str) {
  44.   dump("*** " + str + "\n");
  45. }
  46.  
  47. var Ci = Components.interfaces;
  48. var Cc = Components.classes;
  49. var Cr = Components.results;
  50.  
  51. __defineGetter__("PlacesUtils", function() {
  52.   delete this.PlacesUtils
  53.   var tmpScope = {};
  54.   Components.utils.import("resource://gre/modules/utils.js", tmpScope);
  55.   return this.PlacesUtils = tmpScope.PlacesUtils;
  56. });
  57.  
  58. const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
  59. const DESCRIPTION_ANNO = "bookmarkProperties/description";
  60. const GUID_ANNO = "placesInternal/GUID";
  61. const LMANNO_FEEDURI = "livemark/feedURI";
  62. const LMANNO_SITEURI = "livemark/siteURI";
  63. const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder";
  64. const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
  65. const ORGANIZER_LEFTPANE_VERSION = 6;
  66. const EXCLUDE_FROM_BACKUP_ANNO = "places/excludeFromBackup";
  67.  
  68. //@line 73 "e:\builds\moz2_slave\win32_build\build\browser\components\places\content\utils.js"
  69. // On other platforms, the transferable system converts "\r\n" to "\n".
  70. const NEWLINE = "\r\n";
  71. //@line 76 "e:\builds\moz2_slave\win32_build\build\browser\components\places\content\utils.js"
  72.  
  73. function QI_node(aNode, aIID) {
  74.   return aNode.QueryInterface(aIID);
  75. }
  76. function asVisit(aNode)    { return QI_node(aNode, Ci.nsINavHistoryVisitResultNode);    }
  77. function asFullVisit(aNode){ return QI_node(aNode, Ci.nsINavHistoryFullVisitResultNode);}
  78. function asContainer(aNode){ return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);}
  79. function asQuery(aNode)    { return QI_node(aNode, Ci.nsINavHistoryQueryResultNode);    }
  80.  
  81. var PlacesUIUtils = {
  82.   /**
  83.    * The Microsummary Service
  84.    */
  85.   get microsummaries() {
  86.     delete this.microsummaries;
  87.     return this.microsummaries = Cc["@mozilla.org/microsummary/service;1"].
  88.                                  getService(Ci.nsIMicrosummaryService);
  89.   },
  90.  
  91.   get RDF() {
  92.     delete this.RDF;
  93.     return this.RDF = Cc["@mozilla.org/rdf/rdf-service;1"].
  94.                       getService(Ci.nsIRDFService);
  95.   },
  96.  
  97.   get localStore() {
  98.     delete this.localStore;
  99.     return this.localStore = this.RDF.GetDataSource("rdf:local-store");
  100.   },
  101.  
  102.   get ptm() {
  103.     delete this.ptm;
  104.     return this.ptm = Cc["@mozilla.org/browser/placesTransactionsService;1"].
  105.                       getService(Ci.nsIPlacesTransactionsService);
  106.   },
  107.  
  108.   get clipboard() {
  109.     delete this.clipboard;
  110.     return this.clipboard = Cc["@mozilla.org/widget/clipboard;1"].
  111.                             getService(Ci.nsIClipboard);
  112.   },
  113.  
  114.   get URIFixup() {
  115.     delete this.URIFixup;
  116.     return this.URIFixup = Cc["@mozilla.org/docshell/urifixup;1"].
  117.                            getService(Ci.nsIURIFixup);
  118.   },
  119.  
  120.   get ellipsis() {
  121.     delete this.ellipsis;
  122.     var pref = Cc["@mozilla.org/preferences-service;1"].
  123.                getService(Ci.nsIPrefBranch);
  124.     return this.ellipsis = pref.getComplexValue("intl.ellipsis",
  125.                                                 Ci.nsIPrefLocalizedString).data;
  126.   },
  127.  
  128.   get privateBrowsing() {
  129.     delete this.privateBrowsing;
  130.     return this.privateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
  131.                                   getService(Ci.nsIPrivateBrowsingService);
  132.   },
  133.  
  134.   /**
  135.    * Makes a URI from a spec, and do fixup
  136.    * @param   aSpec
  137.    *          The string spec of the URI
  138.    * @returns A URI object for the spec.
  139.    */
  140.   createFixedURI: function PU_createFixedURI(aSpec) {
  141.     return this.URIFixup.createFixupURI(aSpec, 0);
  142.   },
  143.  
  144.   /**
  145.    * Wraps a string in a nsISupportsString wrapper
  146.    * @param   aString
  147.    *          The string to wrap
  148.    * @returns A nsISupportsString object containing a string.
  149.    */
  150.   _wrapString: function PU__wrapString(aString) {
  151.     var s = Cc["@mozilla.org/supports-string;1"].
  152.             createInstance(Ci.nsISupportsString);
  153.     s.data = aString;
  154.     return s;
  155.   },
  156.  
  157.   /**
  158.    * String bundle helpers
  159.    */
  160.   get _bundle() {
  161.     const PLACES_STRING_BUNDLE_URI =
  162.         "chrome://browser/locale/places/places.properties";
  163.     delete this._bundle;
  164.     return this._bundle = Cc["@mozilla.org/intl/stringbundle;1"].
  165.                           getService(Ci.nsIStringBundleService).
  166.                           createBundle(PLACES_STRING_BUNDLE_URI);
  167.   },
  168.  
  169.   getFormattedString: function PU_getFormattedString(key, params) {
  170.     return this._bundle.formatStringFromName(key, params, params.length);
  171.   },
  172.  
  173.   getString: function PU_getString(key) {
  174.     return this._bundle.GetStringFromName(key);
  175.   },
  176.  
  177.   /**
  178.    * Get a transaction for copying a uri item from one container to another
  179.    * as a bookmark.
  180.    * @param   aData
  181.    *          JSON object of dropped or pasted item properties
  182.    * @param   aContainer
  183.    *          The container being copied into
  184.    * @param   aIndex
  185.    *          The index within the container the item is copied to
  186.    * @returns A nsITransaction object that performs the copy.
  187.    */
  188.   _getURIItemCopyTransaction: function (aData, aContainer, aIndex) {
  189.     return this.ptm.createItem(PlacesUtils._uri(aData.uri), aContainer, aIndex,
  190.                                aData.title, "");
  191.   },
  192.  
  193.   /**
  194.    * Get a transaction for copying a bookmark item from one container to
  195.    * another.
  196.    * @param   aData
  197.    *          JSON object of dropped or pasted item properties
  198.    * @param   aContainer
  199.    *          The container being copied into
  200.    * @param   aIndex
  201.    *          The index within the container the item is copied to
  202.    * @param   [optional] aExcludeAnnotations
  203.    *          Optional, array of annotations (listed by their names) to exclude
  204.    *          when copying the item.
  205.    * @returns A nsITransaction object that performs the copy.
  206.    */
  207.   _getBookmarkItemCopyTransaction:
  208.   function PU__getBookmarkItemCopyTransaction(aData, aContainer, aIndex,
  209.                                               aExcludeAnnotations) {
  210.     var itemURL = PlacesUtils._uri(aData.uri);
  211.     var itemTitle = aData.title;
  212.     var keyword = aData.keyword || null;
  213.     var annos = aData.annos || [];
  214.     // always exclude GUID when copying any item
  215.     var excludeAnnos = [GUID_ANNO];
  216.     if (aExcludeAnnotations)
  217.       excludeAnnos = excludeAnnos.concat(aExcludeAnnotations);
  218.     annos = annos.filter(function(aValue, aIndex, aArray) {
  219.       return excludeAnnos.indexOf(aValue.name) == -1;
  220.     });
  221.     var childTxns = [];
  222.     if (aData.dateAdded)
  223.       childTxns.push(this.ptm.editItemDateAdded(null, aData.dateAdded));
  224.     if (aData.lastModified)
  225.       childTxns.push(this.ptm.editItemLastModified(null, aData.lastModified));
  226.     if (aData.tags) {
  227.       var tags = aData.tags.split(", ");
  228.       // filter out tags already present, so that undo doesn't remove them
  229.       // from pre-existing bookmarks
  230.       var storedTags = PlacesUtils.tagging.getTagsForURI(itemURL, {});
  231.       tags = tags.filter(function (aTag) {
  232.         return (storedTags.indexOf(aTag) == -1);
  233.       }, this);
  234.       if (tags.length)
  235.         childTxns.push(this.ptm.tagURI(itemURL, tags));
  236.     }
  237.  
  238.     return this.ptm.createItem(itemURL, aContainer, aIndex, itemTitle, keyword,
  239.                                annos, childTxns);
  240.   },
  241.  
  242.   /**
  243.    * Gets a transaction for copying (recursively nesting to include children)
  244.    * a folder (or container) and its contents from one folder to another.
  245.    *
  246.    * @param   aData
  247.    *          Unwrapped dropped folder data - Obj containing folder and children
  248.    * @param   aContainer
  249.    *          The container we are copying into
  250.    * @param   aIndex
  251.    *          The index in the destination container to insert the new items
  252.    * @returns A nsITransaction object that will perform the copy.
  253.    */
  254.   _getFolderCopyTransaction:
  255.   function PU__getFolderCopyTransaction(aData, aContainer, aIndex) {
  256.     var self = this;
  257.     function getChildItemsTransactions(aChildren) {
  258.       var childItemsTransactions = [];
  259.       var cc = aChildren.length;
  260.       var index = aIndex;
  261.       for (var i = 0; i < cc; ++i) {
  262.         var txn = null;
  263.         var node = aChildren[i];
  264.  
  265.         // Make sure that items are given the correct index, this will be
  266.         // passed by the transaction manager to the backend for the insertion.
  267.         // Insertion behaves differently if index == DEFAULT_INDEX (append)
  268.         if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
  269.           index = i;
  270.  
  271.         if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
  272.           if (node.livemark && node.annos) // node is a livemark
  273.             txn = self._getLivemarkCopyTransaction(node, aContainer, index);
  274.           else
  275.             txn = self._getFolderCopyTransaction(node, aContainer, index);
  276.         }
  277.         else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR)
  278.           txn = self.ptm.createSeparator(-1, index);
  279.         else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE)
  280.           txn = self._getBookmarkItemCopyTransaction(node, -1, index);
  281.  
  282.         NS_ASSERT(txn, "Unexpected item under a bookmarks folder");
  283.         if (txn)
  284.           childItemsTransactions.push(txn);
  285.       }
  286.       return childItemsTransactions;
  287.     }
  288.  
  289.     // tag folders use tag transactions
  290.     if (aContainer == PlacesUtils.bookmarks.tagsFolder) {
  291.       var txns = [];
  292.       if (aData.children) {
  293.         aData.children.forEach(function(aChild) {
  294.           txns.push(this.ptm.tagURI(PlacesUtils._uri(aChild.uri), [aData.title]));
  295.         }, this);
  296.       }
  297.       return this.ptm.aggregateTransactions("addTags", txns);
  298.     }
  299.     else if (aData.livemark && aData.annos) {
  300.       // Place is a Livemark Container
  301.       return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
  302.     }
  303.     else {
  304.       var childItems = getChildItemsTransactions(aData.children);
  305.       if (aData.dateAdded)
  306.         childItems.push(this.ptm.editItemDateAdded(null, aData.dateAdded));
  307.       if (aData.lastModified)
  308.         childItems.push(this.ptm.editItemLastModified(null, aData.lastModified));
  309.  
  310.       var annos = aData.annos || [];
  311.       annos = annos.filter(function(aAnno) {
  312.         // always exclude GUID when copying any item
  313.         return aAnno.name != GUID_ANNO;
  314.       });
  315.       return this.ptm.createFolder(aData.title, aContainer, aIndex, annos, childItems);
  316.     }
  317.   },
  318.  
  319.   _getLivemarkCopyTransaction:
  320.   function PU__getLivemarkCopyTransaction(aData, aContainer, aIndex) {
  321.     NS_ASSERT(aData.livemark && aData.annos, "node is not a livemark");
  322.     // Place is a Livemark Container
  323.     var feedURI = null;
  324.     var siteURI = null;
  325.     aData.annos = aData.annos.filter(function(aAnno) {
  326.       if (aAnno.name == LMANNO_FEEDURI) {
  327.         feedURI = PlacesUtils._uri(aAnno.value);
  328.         return false;
  329.       }
  330.       else if (aAnno.name == LMANNO_SITEURI) {
  331.         siteURI = PlacesUtils._uri(aAnno.value);
  332.         return false;
  333.       }
  334.       // always exclude GUID when copying any item
  335.       return aAnno.name != GUID_ANNO;
  336.     });
  337.     return this.ptm.createLivemark(feedURI, siteURI, aData.title, aContainer,
  338.                                    aIndex, aData.annos);
  339.   },
  340.  
  341.   /**
  342.    * Constructs a Transaction for the drop or paste of a blob of data into
  343.    * a container.
  344.    * @param   data
  345.    *          The unwrapped data blob of dropped or pasted data.
  346.    * @param   type
  347.    *          The content type of the data
  348.    * @param   container
  349.    *          The container the data was dropped or pasted into
  350.    * @param   index
  351.    *          The index within the container the item was dropped or pasted at
  352.    * @param   copy
  353.    *          The drag action was copy, so don't move folders or links.
  354.    * @returns An object implementing nsITransaction that can perform
  355.    *          the move/insert.
  356.    */
  357.   makeTransaction: function PU_makeTransaction(data, type, container,
  358.                                                index, copy) {
  359.     switch (data.type) {
  360.       case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
  361.         if (copy)
  362.           return this._getFolderCopyTransaction(data, container, index);
  363.         else { // Move the item
  364.           var id = data.folder ? data.folder.id : data.id;
  365.           return this.ptm.moveItem(id, container, index);
  366.         }
  367.         break;
  368.       case PlacesUtils.TYPE_X_MOZ_PLACE:
  369.         if (data.id <= 0) // non-bookmark item
  370.           return this._getURIItemCopyTransaction(data, container, index);
  371.   
  372.         if (copy) {
  373.           // Copying a child of a live-bookmark by itself should result
  374.           // as a new normal bookmark item (bug 376731)
  375.           return this._getBookmarkItemCopyTransaction(data, container, index,
  376.                                                       ["livemark/bookmarkFeedURI"]);
  377.         }
  378.         else
  379.           return this.ptm.moveItem(data.id, container, index);
  380.         break;
  381.       case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
  382.         // There is no data in a separator, so copying it just amounts to
  383.         // inserting a new separator.
  384.         if (copy)
  385.           return this.ptm.createSeparator(container, index);
  386.         // Move the separator otherwise
  387.         return this.ptm.moveItem(data.id, container, index);
  388.         break;
  389.       default:
  390.         if (type == PlacesUtils.TYPE_X_MOZ_URL ||
  391.             type == PlacesUtils.TYPE_UNICODE ||
  392.             type == TAB_DROP_TYPE) {
  393.           var title = (type != PlacesUtils.TYPE_UNICODE) ? data.title :
  394.                                                              data.uri;
  395.           return this.ptm.createItem(PlacesUtils._uri(data.uri),
  396.                                      container, index, title);
  397.         }
  398.     }
  399.     return null;
  400.   },
  401.  
  402.   /**
  403.    * Methods to show the bookmarkProperties dialog in its various modes.
  404.    *
  405.    * The showMinimalAdd* methods open the dialog by its alternative URI. Thus
  406.    * they persist the dialog dimensions separately from the showAdd* methods.
  407.    * Note these variants also do not return the dialog "performed" state since
  408.    * they may not open the dialog modally.
  409.    */
  410.  
  411.   /**
  412.    * Shows the "Add Bookmark" dialog.
  413.    *
  414.    * @param [optional] aURI
  415.    *        An nsIURI object for which the "add bookmark" dialog is
  416.    *        to be shown.
  417.    * @param [optional] aTitle
  418.    *        The default title for the new bookmark.
  419.    * @param [optional] aDescription
  420.             The default description for the new bookmark
  421.    * @param [optional] aDefaultInsertionPoint
  422.    *        The default insertion point for the new item. If set, the folder
  423.    *        picker would be hidden unless aShowPicker is set to true, in which
  424.    *        case the dialog only uses the folder identifier from the insertion
  425.    *        point as the initially selected item in the folder picker.
  426.    * @param [optional] aShowPicker
  427.    *        see above
  428.    * @param [optional] aLoadInSidebar
  429.    *        If true, the dialog will default to load the new item in the
  430.    *        sidebar (as a web panel).
  431.    * @param [optional] aKeyword
  432.    *        The default keyword for the new bookmark. The keyword field
  433.    *        will be shown in the dialog if this is used.
  434.    * @param [optional] aPostData
  435.    *        POST data for POST-style keywords.
  436.    * @param [optional] aCharSet
  437.    *        The character set for the bookmarked page.
  438.    * @return true if any transaction has been performed.
  439.    *
  440.    * Notes:
  441.    *  - the location, description and "loadInSidebar" fields are
  442.    *    visible only if there is no initial URI (aURI is null).
  443.    *  - When aDefaultInsertionPoint is not set, the dialog defaults to the
  444.    *    bookmarks root folder.
  445.    */
  446.   showAddBookmarkUI: function PU_showAddBookmarkUI(aURI,
  447.                                                    aTitle,
  448.                                                    aDescription,
  449.                                                    aDefaultInsertionPoint,
  450.                                                    aShowPicker,
  451.                                                    aLoadInSidebar,
  452.                                                    aKeyword,
  453.                                                    aPostData,
  454.                                                    aCharSet) {
  455.     var info = {
  456.       action: "add",
  457.       type: "bookmark"
  458.     };
  459.  
  460.     if (aURI)
  461.       info.uri = aURI;
  462.  
  463.     // allow default empty title
  464.     if (typeof(aTitle) == "string")
  465.       info.title = aTitle;
  466.  
  467.     if (aDescription)
  468.       info.description = aDescription;
  469.  
  470.     if (aDefaultInsertionPoint) {
  471.       info.defaultInsertionPoint = aDefaultInsertionPoint;
  472.       if (!aShowPicker)
  473.         info.hiddenRows = ["folderPicker"];
  474.     }
  475.  
  476.     if (aLoadInSidebar)
  477.       info.loadBookmarkInSidebar = true;
  478.  
  479.     if (typeof(aKeyword) == "string") {
  480.       info.keyword = aKeyword;
  481.       if (typeof(aPostData) == "string")
  482.         info.postData = aPostData;
  483.       if (typeof(aCharSet) == "string")
  484.         info.charSet = aCharSet;
  485.     }
  486.  
  487.     return this._showBookmarkDialog(info);
  488.   },
  489.  
  490.   /**
  491.    * @see showAddBookmarkUI
  492.    * This opens the dialog with only the name and folder pickers visible by
  493.    * default.
  494.    *
  495.    * You can still pass in the various paramaters as the default properties
  496.    * for the new bookmark.
  497.    *
  498.    * The keyword field will be visible only if the aKeyword parameter
  499.    * was used.
  500.    */
  501.   showMinimalAddBookmarkUI:
  502.   function PU_showMinimalAddBookmarkUI(aURI, aTitle, aDescription,
  503.                                        aDefaultInsertionPoint, aShowPicker,
  504.                                        aLoadInSidebar, aKeyword, aPostData,
  505.                                        aCharSet) {
  506.     var info = {
  507.       action: "add",
  508.       type: "bookmark",
  509.       hiddenRows: ["description"]
  510.     };
  511.     if (aURI)
  512.       info.uri = aURI;
  513.  
  514.     // allow default empty title
  515.     if (typeof(aTitle) == "string")
  516.       info.title = aTitle;
  517.  
  518.     if (aDescription)
  519.       info.description = aDescription;
  520.  
  521.     if (aDefaultInsertionPoint) {
  522.       info.defaultInsertionPoint = aDefaultInsertionPoint;
  523.       if (!aShowPicker)
  524.         info.hiddenRows.push("folderPicker");
  525.     }
  526.  
  527.     if (aLoadInSidebar)
  528.       info.loadBookmarkInSidebar = true;
  529.     else
  530.       info.hiddenRows = info.hiddenRows.concat(["location", "loadInSidebar"]);
  531.  
  532.     if (typeof(aKeyword) == "string") {
  533.       info.keyword = aKeyword;
  534.       // hide the Tags field if we are adding a keyword
  535.       info.hiddenRows.push("tags");
  536.       if (typeof(aPostData) == "string")
  537.         info.postData = aPostData;
  538.       if (typeof(aCharSet) == "string")
  539.         info.charSet = aCharSet;
  540.     }
  541.     else
  542.       info.hiddenRows.push("keyword");
  543.  
  544.     this._showBookmarkDialog(info, true);
  545.   },
  546.  
  547.   /**
  548.    * Shows the "Add Live Bookmark" dialog.
  549.    *
  550.    * @param [optional] aFeedURI
  551.    *        The feed URI for which the dialog is to be shown (nsIURI).
  552.    * @param [optional] aSiteURI
  553.    *        The site URI for the new live-bookmark (nsIURI).
  554.    * @param [optional] aDefaultInsertionPoint
  555.    *        The default insertion point for the new item. If set, the folder
  556.    *        picker would be hidden unless aShowPicker is set to true, in which
  557.    *        case the dialog only uses the folder identifier from the insertion
  558.    *        point as the initially selected item in the folder picker.
  559.    * @param [optional] aShowPicker
  560.    *        see above
  561.    * @return true if any transaction has been performed.
  562.    *
  563.    * Notes:
  564.    *  - the feedURI and description fields are visible only if there is no
  565.    *    initial feed URI (aFeedURI is null).
  566.    *  - When aDefaultInsertionPoint is not set, the dialog defaults to the
  567.    *    bookmarks root folder.
  568.    */
  569.   showAddLivemarkUI: function PU_showAddLivemarkURI(aFeedURI,
  570.                                                     aSiteURI,
  571.                                                     aTitle,
  572.                                                     aDescription,
  573.                                                     aDefaultInsertionPoint,
  574.                                                     aShowPicker) {
  575.     var info = {
  576.       action: "add",
  577.       type: "livemark"
  578.     };
  579.  
  580.     if (aFeedURI)
  581.       info.feedURI = aFeedURI;
  582.     if (aSiteURI)
  583.       info.siteURI = aSiteURI;
  584.  
  585.     // allow default empty title
  586.     if (typeof(aTitle) == "string")
  587.       info.title = aTitle;
  588.  
  589.     if (aDescription)
  590.       info.description = aDescription;
  591.  
  592.     if (aDefaultInsertionPoint) {
  593.       info.defaultInsertionPoint = aDefaultInsertionPoint;
  594.       if (!aShowPicker)
  595.         info.hiddenRows = ["folderPicker"];
  596.     }
  597.     return this._showBookmarkDialog(info);
  598.   },
  599.  
  600.   /**
  601.    * @see showAddLivemarkUI
  602.    * This opens the dialog with only the name and folder pickers visible by
  603.    * default.
  604.    *
  605.    * You can still pass in the various paramaters as the default properties
  606.    * for the new live-bookmark.
  607.    */
  608.   showMinimalAddLivemarkUI:
  609.   function PU_showMinimalAddLivemarkURI(aFeedURI, aSiteURI, aTitle,
  610.                                         aDescription, aDefaultInsertionPoint,
  611.                                         aShowPicker) {
  612.     var info = {
  613.       action: "add",
  614.       type: "livemark",
  615.       hiddenRows: ["feedLocation", "siteLocation", "description"]
  616.     };
  617.  
  618.     if (aFeedURI)
  619.       info.feedURI = aFeedURI;
  620.     if (aSiteURI)
  621.       info.siteURI = aSiteURI;
  622.  
  623.     // allow default empty title
  624.     if (typeof(aTitle) == "string")
  625.       info.title = aTitle;
  626.  
  627.     if (aDescription)
  628.       info.description = aDescription;
  629.  
  630.     if (aDefaultInsertionPoint) {
  631.       info.defaultInsertionPoint = aDefaultInsertionPoint;
  632.       if (!aShowPicker)
  633.         info.hiddenRows.push("folderPicker");
  634.     }
  635.     this._showBookmarkDialog(info, true);
  636.   },
  637.  
  638.   /**
  639.    * Show an "Add Bookmarks" dialog to allow the adding of a folder full
  640.    * of bookmarks corresponding to the objects in the uriList.  This will
  641.    * be called most often as the result of a "Bookmark All Tabs..." command.
  642.    *
  643.    * @param aURIList  List of nsIURI objects representing the locations
  644.    *                  to be bookmarked.
  645.    * @return true if any transaction has been performed.
  646.    */
  647.   showMinimalAddMultiBookmarkUI: function PU_showAddMultiBookmarkUI(aURIList) {
  648.     NS_ASSERT(aURIList.length,
  649.               "showAddMultiBookmarkUI expects a list of nsIURI objects");
  650.     var info = {
  651.       action: "add",
  652.       type: "folder",
  653.       hiddenRows: ["description"],
  654.       URIList: aURIList
  655.     };
  656.     this._showBookmarkDialog(info, true);
  657.   },
  658.  
  659.   /**
  660.    * Opens the properties dialog for a given item identifier.
  661.    *
  662.    * @param aItemId
  663.    *        item identifier for which the properties are to be shown
  664.    * @param aType
  665.    *        item type, either "bookmark" or "folder"
  666.    * @param [optional] aReadOnly
  667.    *        states if properties dialog should be readonly
  668.    * @return true if any transaction has been performed.
  669.    */
  670.   showItemProperties: function PU_showItemProperties(aItemId, aType, aReadOnly) {
  671.     var info = {
  672.       action: "edit",
  673.       type: aType,
  674.       itemId: aItemId,
  675.       readOnly: aReadOnly
  676.     };
  677.     return this._showBookmarkDialog(info);
  678.   },
  679.  
  680.   /**
  681.    * Shows the "New Folder" dialog.
  682.    *
  683.    * @param [optional] aTitle
  684.    *        The default title for the new bookmark.
  685.    * @param [optional] aDefaultInsertionPoint
  686.    *        The default insertion point for the new item. If set, the folder
  687.    *        picker would be hidden unless aShowPicker is set to true, in which
  688.    *        case the dialog only uses the folder identifier from the insertion
  689.    *        point as the initially selected item in the folder picker.
  690.    * @param [optional] aShowPicker
  691.    *        see above
  692.    * @return true if any transaction has been performed.
  693.    */
  694.   showAddFolderUI:
  695.   function PU_showAddFolderUI(aTitle, aDefaultInsertionPoint, aShowPicker) {
  696.     var info = {
  697.       action: "add",
  698.       type: "folder",
  699.       hiddenRows: []
  700.     };
  701.  
  702.     // allow default empty title
  703.     if (typeof(aTitle) == "string")
  704.       info.title = aTitle;
  705.  
  706.     if (aDefaultInsertionPoint) {
  707.       info.defaultInsertionPoint = aDefaultInsertionPoint;
  708.       if (!aShowPicker)
  709.         info.hiddenRows.push("folderPicker");
  710.     }
  711.     return this._showBookmarkDialog(info);
  712.   },
  713.  
  714.   /**
  715.    * Shows the bookmark dialog corresponding to the specified info
  716.    *
  717.    * @param aInfo
  718.    *        Describes the item to be edited/added in the dialog.
  719.    *        See documentation at the top of bookmarkProperties.js
  720.    * @param aMinimalUI
  721.    *        [optional] if true, the dialog is opened by its alternative
  722.    *        chrome: uri.
  723.    *
  724.    * @return true if any transaction has been performed, false otherwise.
  725.    */
  726.   _showBookmarkDialog: function PU__showBookmarkDialog(aInfo, aMinimalUI) {
  727.     var dialogURL = aMinimalUI ?
  728.                     "chrome://browser/content/places/bookmarkProperties2.xul" :
  729.                     "chrome://browser/content/places/bookmarkProperties.xul";
  730.  
  731.     var features;
  732.     if (aMinimalUI)
  733.       features = "centerscreen,chrome,dialog,resizable,modal";
  734.     else
  735.       features = "centerscreen,chrome,modal,resizable=no";
  736.     window.openDialog(dialogURL, "",  features, aInfo);
  737.     return ("performed" in aInfo && aInfo.performed);
  738.   },
  739.  
  740.   /**
  741.    * Returns the closet ancestor places view for the given DOM node
  742.    * @param aNode
  743.    *        a DOM node
  744.    * @return the closet ancestor places view if exists, null otherwsie.
  745.    */
  746.   getViewForNode: function PU_getViewForNode(aNode) {
  747.     var node = aNode;
  748.  
  749.     // the view for a <menu> of which its associated menupopup is a places view,
  750.     // is the menupopup
  751.     if (node.localName == "menu" && !node.node &&
  752.         node.firstChild.getAttribute("type") == "places")
  753.       return node.firstChild;
  754.  
  755.     while (node) {
  756.       // XXXmano: Use QueryInterface(nsIPlacesView) once we implement it...
  757.       if (node.getAttribute("type") == "places")
  758.         return node;
  759.  
  760.       node = node.parentNode;
  761.     }
  762.  
  763.     return null;
  764.   },
  765.  
  766.   /**
  767.    * By calling this before we visit a URL, we will use TRANSITION_TYPED
  768.    * as the transition for the visit to that URL (if we don't have a referrer).
  769.    * This is used when visiting pages from the history menu, history sidebar,
  770.    * url bar, url autocomplete results, and history searches from the places
  771.    * organizer.  If we don't call this, we'll treat those visits as
  772.    * TRANSITION_LINK.
  773.    */
  774.   markPageAsTyped: function PU_markPageAsTyped(aURL) {
  775.     PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory)
  776.                .markPageAsTyped(this.createFixedURI(aURL));
  777.   },
  778.  
  779.   /**
  780.    * By calling this before we visit a URL, we will use TRANSITION_BOOKMARK
  781.    * as the transition for the visit to that URL (if we don't have a referrer).
  782.    * This is used when visiting pages from the bookmarks menu, 
  783.    * personal toolbar, and bookmarks from within the places organizer.
  784.    * If we don't call this, we'll treat those visits as TRANSITION_LINK.
  785.    */
  786.   markPageAsFollowedBookmark: function PU_markPageAsFollowedBookmark(aURL) {
  787.     PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
  788.   },
  789.  
  790.   /**
  791.    * Allows opening of javascript/data URI only if the given node is
  792.    * bookmarked (see bug 224521).
  793.    * @param aURINode
  794.    *        a URI node
  795.    * @return true if it's safe to open the node in the browser, false otherwise.
  796.    *
  797.    */
  798.   checkURLSecurity: function PU_checkURLSecurity(aURINode) {
  799.     if (!PlacesUtils.nodeIsBookmark(aURINode)) {
  800.       var uri = PlacesUtils._uri(aURINode.uri);
  801.       if (uri.schemeIs("javascript") || uri.schemeIs("data")) {
  802.         const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
  803.         var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
  804.                              getService(Ci.nsIStringBundleService).
  805.                              createBundle(BRANDING_BUNDLE_URI).
  806.                              GetStringFromName("brandShortName");
  807.         var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  808.                             getService(Ci.nsIPromptService);
  809.  
  810.         var errorStr = this.getString("load-js-data-url-error");
  811.         promptService.alert(window, brandShortName, errorStr);
  812.         return false;
  813.       }
  814.     }
  815.     return true;
  816.   },
  817.  
  818.   /**
  819.    * Get the description associated with a document, as specified in a <META>
  820.    * element.
  821.    * @param   doc
  822.    *          A DOM Document to get a description for
  823.    * @returns A description string if a META element was discovered with a
  824.    *          "description" or "httpequiv" attribute, empty string otherwise.
  825.    */
  826.   getDescriptionFromDocument: function PU_getDescriptionFromDocument(doc) {
  827.     var metaElements = doc.getElementsByTagName("META");
  828.     for (var i = 0; i < metaElements.length; ++i) {
  829.       if (metaElements[i].name.toLowerCase() == "description" ||
  830.           metaElements[i].httpEquiv.toLowerCase() == "description") {
  831.         return metaElements[i].content;
  832.       }
  833.     }
  834.     return "";
  835.   },
  836.  
  837.   /**
  838.    * Retrieve the description of an item
  839.    * @param aItemId
  840.    *        item identifier
  841.    * @returns the description of the given item, or an empty string if it is
  842.    * not set.
  843.    */
  844.   getItemDescription: function PU_getItemDescription(aItemId) {
  845.     if (PlacesUtils.annotations.itemHasAnnotation(aItemId, DESCRIPTION_ANNO))
  846.       return PlacesUtils.annotations.getItemAnnotation(aItemId, DESCRIPTION_ANNO);
  847.     return "";
  848.   },
  849.  
  850.   /**
  851.    * Gives the user a chance to cancel loading lots of tabs at once
  852.    */
  853.   _confirmOpenInTabs: function PU__confirmOpenInTabs(numTabsToOpen) {
  854.     var pref = Cc["@mozilla.org/preferences-service;1"].
  855.                getService(Ci.nsIPrefBranch);
  856.  
  857.     const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
  858.     var reallyOpen = true;
  859.     if (pref.getBoolPref(kWarnOnOpenPref)) {
  860.       if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
  861.         var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  862.                             getService(Ci.nsIPromptService);
  863.  
  864.         // default to true: if it were false, we wouldn't get this far
  865.         var warnOnOpen = { value: true };
  866.  
  867.         var messageKey = "tabs.openWarningMultipleBranded";
  868.         var openKey = "tabs.openButtonMultiple";
  869.         const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
  870.         var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
  871.                              getService(Ci.nsIStringBundleService).
  872.                              createBundle(BRANDING_BUNDLE_URI).
  873.                              GetStringFromName("brandShortName");
  874.  
  875.         var buttonPressed = promptService.confirmEx(window,
  876.           this.getString("tabs.openWarningTitle"),
  877.           this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
  878.           (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
  879.            + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
  880.           this.getString(openKey), null, null,
  881.           this.getFormattedString("tabs.openWarningPromptMeBranded",
  882.                                   [brandShortName]), warnOnOpen);
  883.  
  884.         reallyOpen = (buttonPressed == 0);
  885.         // don't set the pref unless they press OK and it's false
  886.         if (reallyOpen && !warnOnOpen.value)
  887.           pref.setBoolPref(kWarnOnOpenPref, false);
  888.       }
  889.     }
  890.     return reallyOpen;
  891.   },
  892.  
  893.   /** aItemsToOpen needs to be an array of objects of the form:
  894.     * {uri: string, isBookmark: boolean}
  895.     */
  896.   _openTabset: function PU__openTabset(aItemsToOpen, aEvent) {
  897.     if (!aItemsToOpen.length)
  898.       return;
  899.  
  900.     var urls = [];
  901.     for (var i = 0; i < aItemsToOpen.length; i++) {
  902.       var item = aItemsToOpen[i];
  903.       if (item.isBookmark)
  904.         this.markPageAsFollowedBookmark(item.uri);
  905.       else
  906.         this.markPageAsTyped(item.uri);
  907.  
  908.       urls.push(item.uri);
  909.     }
  910.  
  911.     var browserWindow = getTopWin();
  912.     var where = browserWindow ?
  913.                 whereToOpenLink(aEvent, false, true) : "window";
  914.     if (where == "window") {
  915.       window.openDialog(getBrowserURL(), "_blank",
  916.                         "chrome,all,dialog=no", urls.join("|"));
  917.       return;
  918.     }
  919.  
  920.     var loadInBackground = where == "tabshifted" ? true : false;
  921.     var replaceCurrentTab = where == "tab" ? false : true;
  922.     browserWindow.getBrowser().loadTabs(urls, loadInBackground,
  923.                                         replaceCurrentTab);
  924.   },
  925.  
  926.   openContainerNodeInTabs: function PU_openContainerInTabs(aNode, aEvent) {
  927.     var urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
  928.     if (!this._confirmOpenInTabs(urlsToOpen.length))
  929.       return;
  930.  
  931.     this._openTabset(urlsToOpen, aEvent);
  932.   },
  933.  
  934.   openURINodesInTabs: function PU_openURINodesInTabs(aNodes, aEvent) {
  935.     var urlsToOpen = [];
  936.     for (var i=0; i < aNodes.length; i++) {
  937.       // skip over separators and folders
  938.       if (PlacesUtils.nodeIsURI(aNodes[i]))
  939.         urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
  940.     }
  941.     this._openTabset(urlsToOpen, aEvent);
  942.   },
  943.  
  944.   /**
  945.    * Loads the node's URL in the appropriate tab or window or as a web
  946.    * panel given the user's preference specified by modifier keys tracked by a
  947.    * DOM mouse/key event.
  948.    * @param   aNode
  949.    *          An uri result node.
  950.    * @param   aEvent
  951.    *          The DOM mouse/key event with modifier keys set that track the
  952.    *          user's preferred destination window or tab.
  953.    */
  954.   openNodeWithEvent: function PU_openNodeWithEvent(aNode, aEvent) {
  955.     this.openNodeIn(aNode, whereToOpenLink(aEvent));
  956.   },
  957.   
  958.   /**
  959.    * Loads the node's URL in the appropriate tab or window or as a
  960.    * web panel.
  961.    * see also openUILinkIn
  962.    */
  963.   openNodeIn: function PU_openNodeIn(aNode, aWhere) {
  964.     if (aNode && PlacesUtils.nodeIsURI(aNode) &&
  965.         this.checkURLSecurity(aNode)) {
  966.       var isBookmark = PlacesUtils.nodeIsBookmark(aNode);
  967.  
  968.       if (isBookmark)
  969.         this.markPageAsFollowedBookmark(aNode.uri);
  970.       else
  971.         this.markPageAsTyped(aNode.uri);
  972.  
  973.       // Check whether the node is a bookmark which should be opened as
  974.       // a web panel
  975.       if (aWhere == "current" && isBookmark) {
  976.         if (PlacesUtils.annotations
  977.                        .itemHasAnnotation(aNode.itemId, LOAD_IN_SIDEBAR_ANNO)) {
  978.           var w = getTopWin();
  979.           if (w) {
  980.             w.openWebPanel(aNode.title, aNode.uri);
  981.             return;
  982.           }
  983.         }
  984.       }
  985.       openUILinkIn(aNode.uri, aWhere);
  986.     }
  987.   },
  988.  
  989.   /**
  990.    * Helper for guessing scheme from an url string.
  991.    * Used to avoid nsIURI overhead in frequently called UI functions.
  992.    *
  993.    * @param aUrlString the url to guess the scheme from.
  994.    * 
  995.    * @return guessed scheme for this url string.
  996.    *
  997.    * @note this is not supposed be perfect, so use it only for UI purposes.
  998.    */
  999.   guessUrlSchemeForUI: function PUU_guessUrlSchemeForUI(aUrlString) {
  1000.     return aUrlString.substr(0, aUrlString.indexOf(":"));
  1001.   },
  1002.  
  1003.   /**
  1004.    * Helper for the toolbar and menu views
  1005.    */
  1006.   createMenuItemForNode:
  1007.   function PUU_createMenuItemForNode(aNode, aContainersMap) {
  1008.     var element;
  1009.     var type = aNode.type;
  1010.     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
  1011.       element = document.createElement("menuseparator");
  1012.     else {
  1013.       var iconURI = aNode.icon;
  1014.       var iconURISpec = "";
  1015.       if (iconURI)
  1016.         iconURISpec = iconURI.spec;
  1017.  
  1018.       if (PlacesUtils.uriTypes.indexOf(type) != -1) {
  1019.         element = document.createElement("menuitem");
  1020.         element.className = "menuitem-iconic bookmark-item";
  1021.         element.setAttribute("scheme", this.guessUrlSchemeForUI(aNode.uri));
  1022.       }
  1023.       else if (PlacesUtils.containerTypes.indexOf(type) != -1) {
  1024.         element = document.createElement("menu");
  1025.         element.setAttribute("container", "true");
  1026.  
  1027.         if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
  1028.           element.setAttribute("query", "true");
  1029.           if (PlacesUtils.nodeIsTagQuery(aNode))
  1030.             element.setAttribute("tagContainer", "true");
  1031.           else if (PlacesUtils.nodeIsDay(aNode))
  1032.             element.setAttribute("dayContainer", "true");
  1033.           else if (PlacesUtils.nodeIsHost(aNode))
  1034.             element.setAttribute("hostContainer", "true");
  1035.         }
  1036.         else if (aNode.itemId != -1) {
  1037.           if (PlacesUtils.nodeIsLivemarkContainer(aNode))
  1038.             element.setAttribute("livemark", "true");
  1039.         }
  1040.  
  1041.         var popup = document.createElement("menupopup");
  1042.         popup.setAttribute("placespopup", "true");
  1043.         popup._resultNode = asContainer(aNode);
  1044. //@line 1056 "e:\builds\moz2_slave\win32_build\build\browser\components\places\content\utils.js"
  1045.         // no context menu on mac
  1046.         popup.setAttribute("context", "placesContext");
  1047. //@line 1059 "e:\builds\moz2_slave\win32_build\build\browser\components\places\content\utils.js"
  1048.         element.appendChild(popup);
  1049.         if (aContainersMap)
  1050.           aContainersMap.push({ resultNode: aNode, domNode: popup });
  1051.         element.className = "menu-iconic bookmark-item";
  1052.       }
  1053.       else
  1054.         throw "Unexpected node";
  1055.  
  1056.       element.setAttribute("label", this.getBestTitle(aNode));
  1057.  
  1058.       if (iconURISpec)
  1059.         element.setAttribute("image", iconURISpec);
  1060.     }
  1061.     element.node = aNode;
  1062.     element.node.viewIndex = 0;
  1063.  
  1064.     return element;
  1065.   },
  1066.  
  1067.   cleanPlacesPopup: function PU_cleanPlacesPopup(aPopup) {
  1068.     // Remove places popup children and update markers to keep track of
  1069.     // their indices.
  1070.     var start = aPopup._startMarker != -1 ? aPopup._startMarker + 1 : 0;
  1071.     var end = aPopup._endMarker != -1 ? aPopup._endMarker :
  1072.                                         aPopup.childNodes.length;
  1073.     var items = [];
  1074.     var placesNodeFound = false;
  1075.     for (var i = start; i < end; ++i) {
  1076.       var item = aPopup.childNodes[i];
  1077.       if (item.getAttribute("builder") == "end") {
  1078.         // we need to do this for menus that have static content at the end but
  1079.         // are initially empty, eg. the history menu, we need to know where to
  1080.         // start inserting new items.
  1081.         aPopup._endMarker = i;
  1082.         break;
  1083.       }
  1084.       if (item.node) {
  1085.         items.push(item);
  1086.         placesNodeFound = true;
  1087.       }
  1088.       else {
  1089.         // This is static content...
  1090.         if (!placesNodeFound)
  1091.           // ...at the start of the popup
  1092.           // Initialized in menu.xml, in the base binding
  1093.           aPopup._startMarker++;
  1094.         else {
  1095.           // ...after places nodes
  1096.           aPopup._endMarker = i;
  1097.           break;
  1098.         }
  1099.       }
  1100.     }
  1101.  
  1102.     for (var i = 0; i < items.length; ++i) {
  1103.       aPopup.removeChild(items[i]);
  1104.       if (aPopup._endMarker != -1)
  1105.         aPopup._endMarker--;
  1106.     }
  1107.   },
  1108.  
  1109.   getBestTitle: function PU_getBestTitle(aNode) {
  1110.     var title;
  1111.     if (!aNode.title && PlacesUtils.uriTypes.indexOf(aNode.type) != -1) {
  1112.       // if node title is empty, try to set the label using host and filename
  1113.       // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
  1114.       try {
  1115.         var uri = PlacesUtils._uri(aNode.uri);
  1116.         var host = uri.host;
  1117.         var fileName = uri.QueryInterface(Ci.nsIURL).fileName;
  1118.         // if fileName is empty, use path to distinguish labels
  1119.         title = host + (fileName ?
  1120.                         (host ? "/" + this.ellipsis + "/" : "") + fileName :
  1121.                         uri.path);
  1122.       }
  1123.       catch (e) {
  1124.         // Use (no title) for non-standard URIs (data:, javascript:, ...)
  1125.         title = "";
  1126.       }
  1127.     }
  1128.     else
  1129.       title = aNode.title;
  1130.  
  1131.     return title || this.getString("noTitle");
  1132.   },
  1133.  
  1134.   get leftPaneQueries() {    
  1135.     // build the map
  1136.     this.leftPaneFolderId;
  1137.     return this.leftPaneQueries;
  1138.   },
  1139.  
  1140.   // Get the folder id for the organizer left-pane folder.
  1141.   get leftPaneFolderId() {
  1142.     var leftPaneRoot = -1;
  1143.     var allBookmarksId;
  1144.  
  1145.     // Shortcuts to services.
  1146.     var bs = PlacesUtils.bookmarks;
  1147.     var as = PlacesUtils.annotations;
  1148.  
  1149.     // Get all items marked as being the left pane folder.  We should only have
  1150.     // one of them.
  1151.     var items = as.getItemsWithAnnotation(ORGANIZER_FOLDER_ANNO, {});
  1152.     if (items.length > 1) {
  1153.       // Something went wrong, we cannot have more than one left pane folder,
  1154.       // remove all left pane folders and continue.  We will create a new one.
  1155.       items.forEach(bs.removeItem);
  1156.     }
  1157.     else if (items.length == 1 && items[0] != -1) {
  1158.       leftPaneRoot = items[0];
  1159.       // Check organizer left pane version.
  1160.       var version = as.getItemAnnotation(leftPaneRoot, ORGANIZER_FOLDER_ANNO);
  1161.       if (version != ORGANIZER_LEFTPANE_VERSION) {
  1162.         // If version is not valid we must rebuild the left pane.
  1163.         bs.removeItem(leftPaneRoot);
  1164.         leftPaneRoot = -1;
  1165.       }
  1166.     }
  1167.  
  1168.     var queriesTitles = {
  1169.       "PlacesRoot": "",
  1170.       "History": this.getString("OrganizerQueryHistory"),
  1171.       // TODO: Bug 489681, Tags needs its own string in places.properties
  1172.       "Tags": bs.getItemTitle(PlacesUtils.tagsFolderId),
  1173.       "AllBookmarks": this.getString("OrganizerQueryAllBookmarks"),
  1174.       "Downloads": this.getString("OrganizerQueryDownloads"),
  1175.       "BookmarksToolbar": null,
  1176.       "BookmarksMenu": null,
  1177.       "UnfiledBookmarks": null
  1178.     };
  1179.  
  1180.     if (leftPaneRoot != -1) {
  1181.       // A valid left pane folder has been found.
  1182.       // Build the leftPaneQueries Map.  This is used to quickly access them
  1183.       // associating a mnemonic name to the real item ids.
  1184.       delete this.leftPaneQueries;
  1185.       this.leftPaneQueries = {};
  1186.       var items = as.getItemsWithAnnotation(ORGANIZER_QUERY_ANNO, {});
  1187.       // While looping through queries we will also check for titles validity.
  1188.       for (var i = 0; i < items.length; i++) {
  1189.         var queryName = as.getItemAnnotation(items[i], ORGANIZER_QUERY_ANNO);
  1190.         this.leftPaneQueries[queryName] = items[i];
  1191.         // Titles could have been corrupted or the user could have changed his
  1192.         // locale.  Check title is correctly set and eventually fix it.
  1193.         if (bs.getItemTitle(items[i]) != queriesTitles[queryName])
  1194.           bs.setItemTitle(items[i], queriesTitles[queryName]);
  1195.       }
  1196.       delete this.leftPaneFolderId;
  1197.       return this.leftPaneFolderId = leftPaneRoot;
  1198.     }
  1199.  
  1200.     var self = this;
  1201.     var callback = {
  1202.       // Helper to create an organizer special query.
  1203.       create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
  1204.         let itemId = bs.insertBookmark(aParentId,
  1205.                                        PlacesUtils._uri(aQueryUrl),
  1206.                                        bs.DEFAULT_INDEX,
  1207.                                        queriesTitles[aQueryName]);
  1208.         // Mark as special organizer query.
  1209.         as.setItemAnnotation(itemId, ORGANIZER_QUERY_ANNO, aQueryName,
  1210.                              0, as.EXPIRE_NEVER);
  1211.         // We should never backup this, since it changes between profiles.
  1212.         as.setItemAnnotation(itemId, EXCLUDE_FROM_BACKUP_ANNO, 1,
  1213.                              0, as.EXPIRE_NEVER);
  1214.         // Add to the queries map.
  1215.         self.leftPaneQueries[aQueryName] = itemId;
  1216.         return itemId;
  1217.       },
  1218.  
  1219.       // Helper to create an organizer special folder.
  1220.       create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
  1221.               // Left Pane Root Folder.
  1222.         let folderId = bs.createFolder(aParentId,
  1223.                                        queriesTitles[aFolderName],
  1224.                                        bs.DEFAULT_INDEX);
  1225.         // We should never backup this, since it changes between profiles.
  1226.         as.setItemAnnotation(folderId, EXCLUDE_FROM_BACKUP_ANNO, 1,
  1227.                              0, as.EXPIRE_NEVER);
  1228.         // Disallow manipulating this folder within the organizer UI.
  1229.         bs.setFolderReadonly(folderId, true);
  1230.  
  1231.         if (aIsRoot) {
  1232.           // Mark as special left pane root.
  1233.           as.setItemAnnotation(folderId, ORGANIZER_FOLDER_ANNO,
  1234.                                ORGANIZER_LEFTPANE_VERSION,
  1235.                                0, as.EXPIRE_NEVER);
  1236.         }
  1237.         else {
  1238.           // Mark as special organizer folder.
  1239.           as.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO, aFolderName,
  1240.                            0, as.EXPIRE_NEVER);
  1241.           self.leftPaneQueries[aFolderName] = folderId;
  1242.         }
  1243.         return folderId;
  1244.       },
  1245.  
  1246.       runBatched: function CB_runBatched(aUserData) {
  1247.         delete self.leftPaneQueries;
  1248.         self.leftPaneQueries = { };
  1249.  
  1250.         // Left Pane Root Folder.
  1251.         leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
  1252.  
  1253.         // History Query.
  1254.         this.create_query("History", leftPaneRoot,
  1255.                           "place:type=" +
  1256.                           Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
  1257.                           "&sort=" +
  1258.                           Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
  1259.  
  1260.         // XXX: Downloads.
  1261.  
  1262.         // Tags Query.
  1263.         this.create_query("Tags", leftPaneRoot,
  1264.                           "place:type=" +
  1265.                           Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
  1266.                           "&sort=" +
  1267.                           Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
  1268.  
  1269.         // All Bookmarks Folder.
  1270.         allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
  1271.  
  1272.         // All Bookmarks->Bookmarks Toolbar Query.
  1273.         this.create_query("BookmarksToolbar", allBookmarksId,
  1274.                           "place:folder=TOOLBAR");
  1275.  
  1276.         // All Bookmarks->Bookmarks Menu Query.
  1277.         this.create_query("BookmarksMenu", allBookmarksId,
  1278.                           "place:folder=BOOKMARKS_MENU");
  1279.  
  1280.         // All Bookmarks->Unfiled Bookmarks Query.
  1281.         this.create_query("UnfiledBookmarks", allBookmarksId,
  1282.                           "place:folder=UNFILED_BOOKMARKS");
  1283.       }
  1284.     };
  1285.     bs.runInBatchMode(callback, null);
  1286.  
  1287.     delete this.leftPaneFolderId;
  1288.     return this.leftPaneFolderId = leftPaneRoot;
  1289.   },
  1290.  
  1291.   get allBookmarksFolderId() {
  1292.     // ensure the left-pane root is initialized;
  1293.     this.leftPaneFolderId;
  1294.     delete this.allBookmarksFolderId;
  1295.     return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
  1296.   },
  1297.  
  1298.   /**
  1299.   * Add, update or remove the livemark status menuitem.
  1300.   * @param aPopup
  1301.   *        The livemark container popup
  1302.   */
  1303.   ensureLivemarkStatusMenuItem:
  1304.   function PU_ensureLivemarkStatusMenuItem(aPopup) {
  1305.     var itemId = aPopup._resultNode.itemId;
  1306.  
  1307.     var lmStatus = null;
  1308.     if (PlacesUtils.annotations
  1309.                    .itemHasAnnotation(itemId, "livemark/loadfailed"))
  1310.       lmStatus = "bookmarksLivemarkFailed";
  1311.     else if (PlacesUtils.annotations
  1312.                         .itemHasAnnotation(itemId, "livemark/loading"))
  1313.       lmStatus = "bookmarksLivemarkLoading";
  1314.  
  1315.     if (lmStatus && !aPopup._lmStatusMenuItem) {
  1316.       // Create the status menuitem and cache it in the popup object.
  1317.       aPopup._lmStatusMenuItem = document.createElement("menuitem");
  1318.       aPopup._lmStatusMenuItem.setAttribute("lmStatus", lmStatus);
  1319.       aPopup._lmStatusMenuItem.setAttribute("label", this.getString(lmStatus));
  1320.       aPopup._lmStatusMenuItem.setAttribute("disabled", true);
  1321.       aPopup.insertBefore(aPopup._lmStatusMenuItem,
  1322.                           aPopup.childNodes[aPopup._startMarker + 1]);
  1323.       aPopup._startMarker++;
  1324.     }
  1325.     else if (lmStatus &&
  1326.              aPopup._lmStatusMenuItem.getAttribute("lmStatus") != lmStatus) {
  1327.       // Status has changed, update the cached status menuitem.
  1328.       aPopup._lmStatusMenuItem.setAttribute("label",
  1329.                                             this.getString(lmStatus));
  1330.     }
  1331.     else if (!lmStatus && aPopup._lmStatusMenuItem){
  1332.       // No status, remove the cached menuitem.
  1333.       aPopup.removeChild(aPopup._lmStatusMenuItem);
  1334.       aPopup._lmStatusMenuItem = null;
  1335.       aPopup._startMarker--;
  1336.     }
  1337.   }
  1338. };
  1339.